package edu.kit.aifb.ldstream.learner;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import org.openrdf.model.URI;

import edu.kit.aifb.ldstream.stat.util.IDistributionCheckerFactory;
import edu.kit.aifb.ldstream.stat.util.OctaveCheckerFactory;

import edu.kit.aifb.ldstream.env.Environment;
import edu.kit.aifb.ldstream.learner.dao.Instance;
import edu.kit.aifb.ldstream.learner.dao.PropertyDistributionResult;
import edu.kit.aifb.ldstream.learner.dao.ValueDistributionResult;
import edu.kit.aifb.ldstream.learner.dao.SimpleStatistics;
import edu.kit.aifb.ldstream.learner.dao.Type;
import edu.kit.aifb.ldstream.learner.tasks.Coherence;
import edu.kit.aifb.ldstream.learner.tasks.IndegreeTask;
import edu.kit.aifb.ldstream.learner.tasks.LearnClassesTask;
import edu.kit.aifb.ldstream.learner.tasks.LearnInstancesTask;
import edu.kit.aifb.ldstream.learner.tasks.LearnerTask;
import edu.kit.aifb.ldstream.learner.tasks.NumTriplesTask;
import edu.kit.aifb.ldstream.learner.tasks.OutdegreeTask;
import edu.kit.aifb.ldstream.learner.tasks.PropertyDistributionTask;
import edu.kit.aifb.ldstream.learner.tasks.StructurednessTask;
import edu.kit.aifb.ldstream.learner.tasks.ValueDistributionTask;



public class Learner {

	private Map<Type, HashMap<URI, ValueDistributionResult>> valueResultSet = new HashMap<Type, HashMap<URI, ValueDistributionResult>>();
	private Map<Type, HashSet<PropertyDistributionResult>> propertyResultSet = new HashMap<Type, HashSet<PropertyDistributionResult>>();
	private Map<Type, HashSet<Instance>> classMap;

	private SimpleStatistics simpleStats = new SimpleStatistics();
	private ExecutorService threadPool;
	private IDistributionCheckerFactory distribCheckerFactory;
	
	private boolean inferencing = Environment.INFERENCING;



	public Learner(ExecutorService threadPool) {

		this.classMap = new HashMap<Type, HashSet<Instance>>(1024);
		this.distribCheckerFactory = new OctaveCheckerFactory();
		this.threadPool = threadPool;

	}

	public void learn() {
		
		if(Environment.VERBOSE){
			System.out.println("Started the learning process.");
		}
		/*
		 * Execution order according to the task dependencies
		 * 
		 * 	NumTriples							
		 * 	LearnClasses	=>	LearnInstances	=>	valueDistribution	
		 * 					=>	LearnInstances	=>	structuredness
		 * 						LearnInstances	=>	propertyDistribution
		 * 						LearnInstances	=>	Outdegree
		 * 						LearnInstances	=>	Indegree
		 */
		LearnerTask numTriples = new NumTriplesTask(simpleStats);
//		LearnerTask learnClasses = new LearnClassesTask(simpleStats, this.classMap);
		LearnerTask learnInstances = new LearnInstancesTask(this.threadPool, this.classMap, this.simpleStats, 
																this.distribCheckerFactory.getNewDistributionChecker(), this.inferencing, true);
		Coherence ch = new Coherence();
		LearnerTask structuredness = new StructurednessTask(classMap, ch, this.inferencing);
		LearnerTask indegree = new IndegreeTask(this.simpleStats, this.classMap, this.distribCheckerFactory.getLastInstance());
		LearnerTask outdegree = new OutdegreeTask(this.simpleStats, this.classMap, this.distribCheckerFactory.getLastInstance());
		LearnerTask properties = new PropertyDistributionTask(classMap, this.propertyResultSet, this.inferencing, this.distribCheckerFactory.getNewDistributionChecker());
		LearnerTask valueDistribution = new ValueDistributionTask(this.classMap, this.valueResultSet, 
																	this.distribCheckerFactory.getNewDistributionChecker());

		Future<?>[] futures = new Future<?>[7];

		futures[0] = this.threadPool.submit(numTriples);
/*		futures[1] = this.threadPool.submit(learnClasses);

		try{
			futures[1].get();
		} catch (InterruptedException | ExecutionException e1) {
			e1.printStackTrace();
		}
		*/
		futures[1] = this.threadPool.submit(learnInstances);
		
		try {
			futures[1].get();
		} catch (InterruptedException | ExecutionException e1) {
			e1.printStackTrace();
		}
		futures[2] = this.threadPool.submit(outdegree);
		futures[3] = this.threadPool.submit(indegree);
		futures[4] = this.threadPool.submit(structuredness);
		futures[5] = this.threadPool.submit(valueDistribution);
		futures[6] = this.threadPool.submit(properties);
		
		for(Future<?> f : futures){
			try {
				f.get();
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}
		}
		System.out.println("Coverage " + ch.getCoherence());
		if(Environment.VERBOSE){
			System.out.println("Learning process finished");
		}
		
		if(Environment.VERBOSE){
			System.out.println("Saving the Learning results.");
		}
		storeResultToFile(this.propertyResultSet, Environment.PROP_RESULT_FILE_NAME);
		storeResultToFile(this.valueResultSet, Environment.VALUE_RESULT_FILE_NAME);
		storeResultToFile(this.simpleStats, Environment.SIMPLE_STATS_RESULT_FILE_NAME);
		if(Environment.VERBOSE){
			System.out.println("Learning results saved.");
		}
		
		shutdown();
		
	}
	
	private void storeResultToFile(Object result, String fileName){

		File outputFile = new File(Environment.DEFAULT_OUTPUT_FILE_LOCATION + fileName);
		if(outputFile.exists()){
			// delete the old file
			outputFile.delete();
		}
		boolean created;
		try {
			outputFile.createNewFile();
			created = true;
		} catch (IOException e) {
			System.err.println("Could not create output file");
			created = false;
		}
		if(created){
			FileOutputStream fos = null;
			ObjectOutputStream oos = null;
			try {
				fos = new FileOutputStream(outputFile);
				oos = new ObjectOutputStream(fos);
				oos.writeObject(result);
			} catch (IOException e) {
				e.printStackTrace();
			}finally{
				try {
					oos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	private void shutdown(){
		this.distribCheckerFactory.shutdown();
	}

}
